#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

//第24讲：预处理详解
//1. 预定义符号
//C语⾔设置了⼀些预定义符号，可以直接使⽤，预定义符号也是在预处理期间处理的。
//__FILE__ //进⾏编译的源⽂件
//__LINE__ //⽂件当前的⾏号
//__DATE__ //⽂件被编译的⽇期
//__TIME__ //⽂件被编译的时间
//__STDC__ //如果编译器遵循ANSI C，其值为1，否则未定义
//int main()
//{
//	printf("%s\n", __FILE__);
//	printf("%d\n", __LINE__);
//	printf("%s\n", __DATE__);
//	printf("%s\n", __TIME__);
//	//printf("%d\n", __STDC__);// VS不完全遵循ANSI C
//	return 0;
//}



//2. #define
//#define可以定义常量，也可以定义一串代码
//#define MAX 1000			定义MAX为1000
//#define reg register		为 register这个关键字，创建⼀个简短的名字
//#define do_forever for(;;)⽤更形象的符号来替换⼀种实现
//#define CASE break;case	在写case语句的时候⾃动把 break写上。
//如果定义的 stuff过⻓，可以分成⼏⾏写，除了最后⼀⾏外，每⾏的后⾯都加⼀个反斜杠(续⾏符)。
//#define DEBUG_PRINT printf("file:%s\tline:%d\t \
// date:%s\ttime:%s\n" ,\
// __FILE__,__LINE__ , \
// __DATE__,__TIME__ )
//你甚至可以这样写:
//#define 整型 int
//#define 主函数 main
//#define 打印 printf
//整型 主函数()
//{
//	打印("%d", 1);
//}
//面向中文编程哈哈哈哈哈哈

//define的时候后面补药加分号
//如果加分号，例如: #define MAX 1000;
//那么打印的时候：printf("%d",MAX);
//预处理后生成的.i文件就变成这样:printf("%d",1000;); 显然这是不符合语法的



//3. #define定义宏
//#define 机制包括了⼀个规定，允许把参数替换到⽂本中，这种实现通常称为宏（macro）或定义宏（define macro）。
//下⾯是宏的申明⽅式：
//#define name( parament-list ) stuff
//其中的 parament - list 是⼀个由逗号隔开的符号表，它们可能出现在stuff中。
//注意：
//参数列表的左括号必须与name紧邻，如果两者之间有任何空⽩存在，参数列表就会被解释为stuff的⼀部分。

//举例:
//#define SQUARE(x) x * x
//a传给SQUARE，这里的x都要替换成a 宏就是来完成替换的，这里不需要说明x的类型，只管把x替换成传来的东西就可以
//int main()
//{
//	int a = 5;
//	int ret = SQUARE(a);
//	//替换后上面这一句代码就变成了:
//	//int ret = a*a
//	//那如果传入的是a+1呢？结果会是36吗？
//	ret = SQUARE(a + 1);
//	//刚刚说了，宏是用来替换的，把x都替换成a+1,那么这句宏就变成了:a+1*a+1
//	//那么ret=a+1*a+1 结果就是11
//	//但是这达不到我的预期呀！我想算的是6*6=36,那能有神魔办法呢？
//	//把a+1*a+1换成(a+1)*(a+1)不就好了吗，也就是我们可以把x看成一个整体，给它加上一个括号就可以了:#define SQUARE(x) ((x)*(x)) -- 整体最好也要带上括号
//	printf("%d", ret);
//	return 0;
//}
//所以这个故事告诉我们，在定义宏的时候，补药吝啬括号，如果缺少括号的话就有可能因为宏的参数部分操作符的优先级，和宏体内操作符
//的优先级影响了表达式求值的顺序,而达不到我们的预期



//4. 带有副作⽤的宏参数
//当宏参数在宏的定义中出现超过⼀次的时候，如果参数带有副作⽤，那么你在使⽤这个宏的时候就可
//能出现危险，导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果。
//例如：
//x + 1;//不带副作⽤
//x++;//带有副作⽤
//#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
//int main()
//{
//	int x = 5;
//	int y = 8;
//	int z = MAX(x++, y++);
//	//这⾥我们得知道预处理器处理之后的结果是什么：
//	//z = ((x++) > (y++) ? (x++) : (y++));
//	//所以输出的结果是：x=6 y=10 z=9
//	printf("x=%d y=%d z=%d\n", x, y, z);
//	return 0;
//}


//5. 宏替换的规则
//在程序中扩展#define定义符号和宏时，需要涉及⼏个步骤。
//1. 在调⽤宏时，⾸先对参数进⾏检查，看看是否包含任何由#define定义的符号。如果是，它们⾸先被替换。
//2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏，参数名被他们的值所替换
//3. 最后，再次对结果⽂件进⾏扫描，看看它是否包含任何由#define定义的符号。如果是，就重复上
//述处理过程。
//例如:
//#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
//#define M 10
//int main()
//{
//	int x = 5;
//	int y = 8;
//	int z = MAX(x, M);
//	printf("x=%d y=%d z=%d\n", x, y, z);
//	return 0;
//}
//第一步：先替换参数里的内容:
//int z=MAX(x,M) --> int z=MAX(x,10)
//第二步：宏的参数名被替换：
//#define MAX(a, b) ( (a) > (b) ? (a) : (b) ) --> #define MAX(a, b) ( (x) > (10) ? (x) : (10) )
//最后：
//int z=MAX(x,10) --> int z= ((x) > (10) ? (x) : (10))
//注意：
//1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏，不能出现递归。
// 例如:#define MAX( a,b ) ((a)>(b)?(MAX(a,b):b)) //定义里不能再出现宏本身
//2. 当预处理器搜索#define定义的符号的时候，字符串常量的内容并不被搜索。
//例如:MAX(10,M),MAX是宏，这个M也可以是由宏定义的常量
//例如:"MAX"就不会被看作是宏，这是一个字符串


//6. 宏函数的对⽐
//宏通常被应⽤于执⾏简单的运算。
//⽐如在两个数中找出较⼤的⼀个时，写成下⾯的宏，更有优势⼀些。
//#define MAX(a, b) ((a)>(b)?(a):(b))
//宏的方式:只有执行运算的时候消耗时间

//int Max(int a, int b)
//{
//	return a + b;
//}
//函数的方式：
//1.调用函数
//2.执行运算
//3.函数返回
//这三步都需要时间
//所以在执行简单运算的时候使用宏是更有优势的,原因有2： 
//1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐
//函数在程序的规模和速度⽅⾯更胜⼀筹。
//2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之
//这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆关
//的。

//但是如果是进行一个大型的复杂的运算呢？我们假设：
//调用函数需要1ms   执行运算需要100ms   函数返回需要1ms
//所以函数的方式需要102ms  宏的方式需要100ms
//这种情况下这两种方式所需要的时间差不多
//但是由于在更复杂的运算里面使用宏更容易造成运算符优先级的问题，所以此时更推荐用函数

//下面是宏相较于函数的劣势
//1. 每次使⽤宏的时候，⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短，否则可能⼤幅度增加程序的⻓度。
//2. 宏是没法调试的。
//3. 宏由于类型⽆关，也就不够严谨。
//4. 宏可能会带来运算符优先级的问题，导致程容易出现错。


//宏有时候可以做函数做不到的事情。⽐如：宏的参数可以出现类型，但是函数做不到。
//#define MALLOC(num, type) (type*)malloc(num,sizeof(type))
//使⽤
//int*ptr=MALLOC(10, int);//类型作为参数
//预处理指令替换之后：
//int*ptr=(int*)malloc(10 sizeof(int));



//7. #和##
//7.1 #运算符
//#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
//#运算符所执⾏的操作可以理解为”字符串化“。
//当我们有⼀个变量 int a = 10; 的时候，我们想打印出： the value of a is 10 .
//就可以写：
//#define Print(a,format) printf("the value of "#a" is "#format,a)
//int main()
//{
//	int a = 10;
//	Print(a, %d);
//	return 0;
//}
//


//7.2 ## 运算符
//## 可以把位于它两边的符号合成⼀个符号，它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称为记号粘合
//这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的
//#define GENERIC_MAX(type)						\
//type type##_max(type x,type y)					\
//{												\
//	return x>y?x:y;								\
//}												//这个相当于函数的模板，你想要什么样类型的比较函数就输入所需类型就可以了,
//GENERIC_MAX(int);								//可以批量生产一些相似的函数
//GENERIC_MAX(float);	//定义函数
//int main()
//{
//	printf("%d\n", int_max(3, 5));
//	printf("%f\n", float_max(3.5, 5.5));
//	return 0;
//}
//但是在实际开发过程中##使⽤的很少，很难取出⾮常贴切的例⼦



//8. 命名约定
//⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
//那我们平时的⼀个习惯是：
//把宏名全部⼤写
//函数名不要全部⼤写



//9. #undef
//这条指令⽤于移除⼀个宏定义。
//#define MAX 10
//int main()
//{
//	printf("%d", MAX);
//#undef MAX
//	printf("%d", MAX); //这里就会报错
//	return 0;
//}



//10. 命令⾏定义
//许多C 的编译器提供了⼀种能⼒，允许在命令⾏中定义符号。⽤于启动编译过程。
//例如：当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候，这个特性有点⽤处。（假定某
//个程序中声明了⼀个某个⻓度的数组，如果机器内存有限，我们需要⼀个很⼩的数组，但是另外⼀个
//机器内存⼤些，我们需要⼀个数组能够⼤些。）
//这个是在linux环境底下演示出来的，窝的VS演示不出来，也有可能是窝自己太菜了T^T



//11. 条件编译
//在编译⼀个程序的时候我们如果要将⼀条语句（⼀组语句）编译或者放弃是很⽅便的。因为我们有条
//件编译指令。
//⽐如说：
//调试性的代码，删除可惜，保留⼜碍事，所以我们可以选择性的编译。
//常⻅的条件编译指令

//1.#if 常量表达式
//....
//#endif
//常量表达式由预处理器求值。
//#define MAX 1
//int main()
//{
//#if MAX
//	printf("yes");
//#endif
//	return 0;
//}

//2.多个分⽀的条件编译
//#if 常量表达式
//...
//#elif 常量表达式
//...
//#else
//...
//#endif
//#define MAX 10
//int main()
//{
//#if MAX>9
//	printf("hehe\n");
//#elif MAX>10
//	printf("haha");
//#elif MAX>11
//	printf("yee");
//#else
//	printf("omg!");
//#endif
//	return 0;
//}

//3.判断是否被定义
//#if defined(symbol)
//#ifdef symbol
//#if !defined(symbol)
//#ifndef symbol
//#define MAX 0
//int main()
//{
//#if defined(MAX)
//	printf("defined\n");
//#else  //这里也可以写成 #if !defined(MAX)
//	printf("not defined\n");
//#endif
//
//#ifdef MAX
//	printf("defined\n");
//#else	//这里也可以写成 #ifndef MAX
//	printf("not defined\n");
//#endif
//	return 0;
//}

//4.嵌套指令
//#if defined(OS_UNIX)
//	#ifdef OPTION1
//		unix_version_option1();
//	#endif
//	#ifdef OPTION2
//		unix_version_option2();
//	#endif
//#elif defined(OS_MSDOS)
//	#ifdef OPTION2
//		msdos_version_option2();
//	#endif
//#endif



//12. 头⽂件的包含
//12.1 头⽂件被包含的⽅式：
//12.1.1 本地⽂件包含
//#include "filename"
//查找策略：先在源⽂件所在⽬录下查找，如果该头⽂件未找到，编译器就像查找库函数头⽂件⼀样在
//标准位置查找头⽂件。
//如果找不到就提⽰编译错误。

//12.1.2 库⽂件包含
//#include <filename.h>
//查找头⽂件直接去标准路径下去查找，如果找不到就提⽰编译错误。
//这样是不是可以说，对于库⽂件也可以使⽤ “” 的形式包含？
//答案是肯定的，可以，但是这样做查找的效率就低些，当然这样也不容易区分是库⽂件还是本地⽂件了。


//12.2 嵌套⽂件包含
//我们已经知道， #include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的地⽅⼀样。
//这种替换的⽅式很简单：预处理器先删除这条指令，并⽤包含⽂件的内容替换。
//⼀个头⽂件被包含10次，那就实际被编译10次，如果重复包含，对编译的压⼒就⽐较⼤。
//如何解决头⽂件被重复引⼊的问题？答案：条件编译。
//假设这里现在是头文件:
//#ifndef __Add_H__
//#define __Add_H__
//int Add(int x, int y)
//{
//	return x + y;
//}
//#endif
//当多次包含头文件的时候，实际就包含了一次头文件，因为第一次包含头文件#ifndef为真，包含一次,在这之后#ifndef就为假，不再执行后面内容
//当然我们在创建.h文件的时候可以发现第一行有#pragma once
//这段代码的意思就是防止头文件被重复多次包含
